﻿using System;
using System.Collections.Generic;
using System.Globalization;
using System.Reflection;
using System.Text;

namespace Developex.DatabaseWrapper.Fields
{
	/// <summary>
	/// Foreign object
	/// </summary>
	/// <typeparam name="T">field type</typeparam>
	/// <typeparam name="FRow">Foreign object type</typeparam>
	/// <remarks>
	/// Most probably you will need to override next members:
	/// - Object
	/// </remarks>
	public abstract class ForeignKeyFieldBase<T, FRow> : Field<T>, IForeignObject where FRow : Row, new()
	{
		#region Virtual Functions

		///<summary>
		/// Forein Object field
		///</summary>
		public virtual FRow Object
		{
			get
			{
				if (foreignObject == null)
				{
					if (objectCretionParameters != null)
					{
						foreignObject = (FRow)Activator.CreateInstance(typeof(FRow), BindingFlags.Instance | BindingFlags.CreateInstance | BindingFlags.Public | BindingFlags.NonPublic, null, objectCretionParameters, CultureInfo.CurrentCulture);
					}
					else foreignObject = new FRow();
					foreignObject.SetParent(Parent, this);
				}
				return foreignObject;
			}
		}

		#endregion

		#region Abstract members

		///<summary>
		/// Get join expression for this field
		///</summary>
		///<returns></returns>
		public abstract string GetJoinExpression();

		#endregion

		#region Fields

		/// <summary>
		/// 
		/// </summary>
		protected bool simpleNamingMode = false;
		/// <summary>
		/// 
		/// </summary>
		protected bool doFullLoad = false;
		/// <summary>
		/// 
		/// </summary>
		protected object[] objectCretionParameters;
		/// <summary>
		/// 
		/// </summary>
		protected List<IFieldBase> _FieldsToLoad;
		/// <summary>
		/// 
		/// </summary>
		protected FRow foreignObject = null;

		#endregion

		#region Public Properties

		/// <summary>
		/// Load all fields
		/// </summary>
		public bool DoFullLoad
		{
			get { return doFullLoad; }
			set { doFullLoad = value; }
		}

		/// <summary>
		/// If true, join table will not be changed
		/// </summary>
		public bool SimpleNamingMode
		{
			get { return simpleNamingMode; }
			set { simpleNamingMode = value; }
		}

		///<summary>
		/// Custom parameters for Object creation
		///</summary>
		public object[] ObjectCretionParameters
		{
			get { return objectCretionParameters; }
			set
			{
				objectCretionParameters = value.Length == 0 ? null : value;
			}
		}

		#endregion

		#region Constructors

		/// <summary>
		/// Constructor
		/// </summary>
		/// <param name="name"></param>
		/// <param name="flags"></param>
		/// <param name="insertDefaultValue"></param>
		/// <param name="doFullLoad"></param>
		protected ForeignKeyFieldBase(string name, T insertDefaultValue, FieldFlags flags, bool doFullLoad)
			: base(name, flags, insertDefaultValue)
		{
			this.doFullLoad = doFullLoad;
		}

		/// <summary>
		/// Constructor
		/// </summary>
		/// <param name="name"></param>
		/// <param name="flags"></param>
		/// <param name="doFullLoad"></param>
		protected ForeignKeyFieldBase(string name, FieldFlags flags, bool doFullLoad)
			: base(name, flags)
		{
			this.doFullLoad = doFullLoad;
		}

		/// <summary>
		/// Constructor
		/// </summary>
		/// <param name="name"></param>
		/// <param name="doFullLoad"></param>
		protected ForeignKeyFieldBase(string name, bool doFullLoad)
			: base(name)
		{
			this.doFullLoad = doFullLoad;
		}

		/// <summary>
		/// Constructor
		/// </summary>
		/// <param name="name"></param>
		/// <param name="flags"></param>
		/// <param name="insertDefaultValue"></param>
		/// <param name="doFullLoad"></param>
		/// <param name="simpleNamingMode"></param>
		protected ForeignKeyFieldBase(string name, T insertDefaultValue, FieldFlags flags, bool doFullLoad, bool simpleNamingMode)
			: base(name, flags, insertDefaultValue)
		{
			this.doFullLoad = doFullLoad;
			this.simpleNamingMode = simpleNamingMode;
		}

		/// <summary>
		/// Constructor
		/// </summary>
		/// <param name="name"></param>
		/// <param name="flags"></param>
		/// <param name="doFullLoad"></param>
		/// <param name="simpleNamingMode"></param>
		protected ForeignKeyFieldBase(string name, FieldFlags flags, bool doFullLoad, bool simpleNamingMode)
			: base(name, flags)
		{
			this.doFullLoad = doFullLoad;
			this.simpleNamingMode = simpleNamingMode;
		}

		/// <summary>
		/// Constructor
		/// </summary>
		/// <param name="name"></param>
		/// <param name="doFullLoad"></param>
		/// <param name="simpleNamingMode"></param>
		protected ForeignKeyFieldBase(string name, bool doFullLoad, bool simpleNamingMode)
			: base(name)
		{
			this.doFullLoad = doFullLoad;
			this.simpleNamingMode = simpleNamingMode;
		}

		/// <summary>
		/// Constructor
		/// </summary>
		/// <param name="name"></param>
		protected ForeignKeyFieldBase(string name)
			: base(name)
		{
		}

		/// <summary>
		/// Constructor
		/// </summary>
		/// <param name="name"></param>
		/// <param name="flags"></param>
		protected ForeignKeyFieldBase(string name, FieldFlags flags)
			: base(name, flags)
		{
		}

		/// <summary>
		/// Constructor
		/// </summary>
		/// <param name="name"></param>
		/// <param name="flags"></param>
		/// <param name="insertDefaultValue"></param>
		protected ForeignKeyFieldBase(string name, FieldFlags flags, T insertDefaultValue)
			: base(name, flags, insertDefaultValue)
		{
		}


		/// <summary>
		/// Constructor
		/// </summary>
		/// <param name="name"></param>
		/// <param name="flags"></param>
		/// <param name="insertDefaultValue"></param>
		/// <param name="doFullLoad"></param>
		/// <param name="cretionParameters"></param>
		protected ForeignKeyFieldBase(string name, T insertDefaultValue, FieldFlags flags, bool doFullLoad, object[] cretionParameters)
			: base(name, flags, insertDefaultValue)
		{
			this.doFullLoad = doFullLoad;
			this.objectCretionParameters = cretionParameters;
		}

		/// <summary>
		/// Constructor
		/// </summary>
		/// <param name="name"></param>
		/// <param name="flags"></param>
		/// <param name="doFullLoad"></param>
		/// <param name="cretionParameters"></param>
		protected ForeignKeyFieldBase(string name, FieldFlags flags, bool doFullLoad, object[] cretionParameters)
			: base(name, flags)
		{
			this.doFullLoad = doFullLoad;
			this.objectCretionParameters = cretionParameters;
		}

		/// <summary>
		/// Constructor
		/// </summary>
		/// <param name="name"></param>
		/// <param name="doFullLoad"></param>
		/// <param name="cretionParameters"></param>
		protected ForeignKeyFieldBase(string name, bool doFullLoad, object[] cretionParameters)
			: base(name)
		{
			this.doFullLoad = doFullLoad;
			this.objectCretionParameters = cretionParameters;
		}

		/// <summary>
		/// Constructor
		/// </summary>
		/// <param name="name"></param>
		/// <param name="flags"></param>
		/// <param name="insertDefaultValue"></param>
		/// <param name="doFullLoad"></param>
		/// <param name="simpleNamingMode"></param>
		/// <param name="cretionParameters"></param>
		protected ForeignKeyFieldBase(string name, T insertDefaultValue, FieldFlags flags, bool doFullLoad, bool simpleNamingMode, object[] cretionParameters)
			: base(name, flags, insertDefaultValue)
		{
			this.doFullLoad = doFullLoad;
			this.simpleNamingMode = simpleNamingMode;
			this.objectCretionParameters = cretionParameters;
		}

		/// <summary>
		/// Constructor
		/// </summary>
		/// <param name="name"></param>
		/// <param name="flags"></param>
		/// <param name="doFullLoad"></param>
		/// <param name="simpleNamingMode"></param>
		/// <param name="cretionParameters"></param>
		protected ForeignKeyFieldBase(string name, FieldFlags flags, bool doFullLoad, bool simpleNamingMode, object[] cretionParameters)
			: base(name, flags)
		{
			this.doFullLoad = doFullLoad;
			this.simpleNamingMode = simpleNamingMode;
			this.objectCretionParameters = cretionParameters;
		}

		/// <summary>
		/// Constructor
		/// </summary>
		/// <param name="name"></param>
		/// <param name="doFullLoad"></param>
		/// <param name="simpleNamingMode"></param>
		/// <param name="cretionParameters"></param>
		protected ForeignKeyFieldBase(string name, bool doFullLoad, bool simpleNamingMode, object[] cretionParameters)
			: base(name)
		{
			this.doFullLoad = doFullLoad;
			this.simpleNamingMode = simpleNamingMode;
			this.objectCretionParameters = cretionParameters;
		}

		/// <summary>
		/// Constructor
		/// </summary>
		/// <param name="name"></param>
		/// <param name="cretionParameters"></param>
		protected ForeignKeyFieldBase(string name, object[] cretionParameters)
			: base(name)
		{
			this.objectCretionParameters = cretionParameters;
		}

		/// <summary>
		/// Constructor
		/// </summary>
		/// <param name="name"></param>
		/// <param name="flags"></param>
		/// <param name="cretionParameters"></param>
		protected ForeignKeyFieldBase(string name, FieldFlags flags, object[] cretionParameters)
			: base(name, flags)
		{
			this.objectCretionParameters = cretionParameters;
		}

		/// <summary>
		/// Constructor
		/// </summary>
		/// <param name="name"></param>
		/// <param name="flags"></param>
		/// <param name="insertDefaultValue"></param>
		/// <param name="cretionParameters"></param>
		protected ForeignKeyFieldBase(string name, FieldFlags flags, T insertDefaultValue, object[] cretionParameters)
			: base(name, flags, insertDefaultValue)
		{
			this.objectCretionParameters = cretionParameters;
		}

		#endregion

		#region Implementation of IForeignObject

		/// <summary>
		/// Get foreign object
		/// </summary>
		public virtual Row GetForeignObject()
		{
			return Object;
		}

		/// <summary>
		/// Is foreign object already created
		/// </summary>
		public virtual bool IsForeignObjectCreated
		{
			get { return foreignObject != null; }
		}

		/// <summary>
		/// Get prefix for table name for foreign object in select query
		/// </summary>
		/// <returns></returns>
		public virtual string GetJoinPrefix()
		{
			string prefix = Parent.JoinPrefix;
			if (SimpleNamingMode)
				return prefix;
			return prefix + Name + Connection.FieldNameDelimiter;
		}

		/// <summary>
		/// List of fields to load from foreign table
		/// </summary>
		public virtual List<IFieldBase> FieldsToLoad
		{
			get
			{
				if (_FieldsToLoad == null)
					_FieldsToLoad = new List<IFieldBase>();
				return _FieldsToLoad;
			}
		}

		/// <summary>
		/// Set custom parameters for Object creation
		/// </summary>
		/// <param name="parameters"></param>
		public virtual void SetObjectCreationParameters(params object[] parameters)
		{
			ObjectCretionParameters = parameters;
		}

		/// <summary>
		/// Add custom parameters for Object creation
		/// </summary>
		/// <param name="parameters"></param>
		public virtual void AddObjectCreationParameters(params object[] parameters)
		{
			if (parameters == null || parameters.Length == 0)
				return;
			if (objectCretionParameters == null)
				objectCretionParameters = parameters;
			else
			{
				object[] newParameters = new object[objectCretionParameters.Length + parameters.Length];
				Array.Copy(objectCretionParameters, newParameters, objectCretionParameters.Length);
				Array.Copy(parameters, 0, newParameters, objectCretionParameters.Length, parameters.Length);
				objectCretionParameters = newParameters;
			}
		}

		///<summary>
		/// Is foregn fields presented 
		///</summary>
		public virtual bool IsExist
		{
			get { return GetValue() != null; }
		}

		#endregion

		#region Overrides

		///<summary>
		///</summary>
		///<returns></returns>
		/// <remarks>overrided in ForeignField to avoid adding not used joins</remarks>
		public override bool NeedToPrepare()
		{
			int fieldsToLoadCount = doFullLoad ? Object.Fields.Count : FieldsToLoad.Count;
			return fieldsToLoadCount > 0;
		}

		/// <summary>
		/// Add joins and etc.
		/// </summary>
		/// <param name="qb">query builder</param>
		/// <remarks>don't forget to call qb.</remarks>
		public override void PrepareForSelectQuery(QueryBuilder qb)
		{
			// add join
			string join = GetJoinExpression();
			if (!string.IsNullOrEmpty(join))
				qb.Join.Add(join);

			// add sub-fields
			if (doFullLoad)
				HelperFunctions.AddToQueryBuilder(qb, Object.Fields.Values);
			else
				HelperFunctions.AddToQueryBuilder(qb, FieldsToLoad);
		}

		/// <summary>
		/// Clone
		/// </summary>
		/// <returns></returns>
		public override object Clone()
		{
			ForeignKeyFieldBase<T, FRow> field = (ForeignKeyFieldBase<T, FRow>)base.Clone();
			field.simpleNamingMode = simpleNamingMode;
			field.doFullLoad = doFullLoad;
			// Note: light-weight clone
			field.objectCretionParameters = objectCretionParameters;
			field.foreignObject = foreignObject;
			field._FieldsToLoad = _FieldsToLoad;
			return field;
		}

		/// <summary>
		/// get/set value
		/// </summary>
		public override T Value
		{
			get { return base.Value; }
			set
			{
				if (value == null)
					foreignObject = null;
				if (foreignObject != null && foreignObject.PrimaryKeyField != null)
				{
					foreignObject.PrimaryKeyField.SetValue(value);
				}
				base.Value = value;
			}
		}

		#endregion
	}
}
